# Copyright (C) 2005-2006 Frederic Back <fredericback@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.

import gtk
import gobject
from _geodata import Layer, LayerStyle, Colour, colour_from_gdk
from _guicore import PGButler
from _guitools import createLabel, createColourPixbuf, createTreeview
from _pane_layer import createLayerIcon


#-------------------------------------------------------------------------------        
class Wizard( gtk.Dialog ):
    """ 
    A base class for wizards (instead of using a gnome druid).
    It behaves like a gtk.Dialog: Just call run() to run it. 
    """
    
    __gsignals__ = {
        'forward': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,)),
        'back': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,)),
    }

    def __init__(self, title=None, parent=None, flags=0):
        gtk.Dialog.__init__(self, title, parent, flags)
        self.notebook = gtk.Notebook()
        self.notebook.set_show_tabs(False)
        self.notebook.set_show_border(False)
        self.notebook.set_border_width(6)
        self.vbox.add(self.notebook)
    
        # add cancel, prev and next buttons
        button = gtk.Button(None,gtk.STOCK_CANCEL)
        button.connect("clicked",lambda w: self.response(gtk.RESPONSE_REJECT))
        self.action_area.pack_end(button, False)

        self.prevButton = gtk.Button(None,gtk.STOCK_GO_BACK)
        self.prevButton.connect("clicked",self.__prev)
        self.action_area.pack_end(self.prevButton, False)

        self.nextButton = gtk.Button(None,gtk.STOCK_GO_FORWARD)
        self.nextButton.connect("clicked",self.__next)
        self.action_area.pack_end(self.nextButton, False)
        self.finishStockId = _("Finish")
        
        # finalize
        self.action_area.show_all()
        self.set_default_size(400,320)
        
    def append_page(self, child):
        self.notebook.append_page(child)
        self.notebook.show_all()
    
    def finish(self):
        pass
    
    def run(self, child=None):
        """ Starts the dialog, just like with gtk.Dialog.
            if 'child' is specified, the wizard will start at that widget's page.
        """
        if child: self.notebook.set_current_page( self.notebook.page_num(child) )
        else: self.notebook.set_current_page(0)
        self.prevButton.set_sensitive(False)
        if self.notebook.get_n_pages() == 1:
            self.nextButton.set_label(self.finishStockId)
        gtk.Dialog.run(self)
       
    def __prev(self, widget):
        oldpage = self.notebook.get_current_page()
        self.notebook.prev_page()
        newpage = self.notebook.get_current_page()
        
        if oldpage != newpage:
            self.emit("back",newpage)
            self.nextButton.set_label(gtk.STOCK_GO_FORWARD)
            if newpage == 0: self.prevButton.set_sensitive(False)
        
    def __next(self, widget):      
        oldpage = self.notebook.get_current_page()
        if oldpage+1 == self.notebook.get_n_pages():
            self.finish()
            self.response(gtk.RESPONSE_ACCEPT)
            return
            
        self.notebook.next_page()
        
        newpage = self.notebook.get_current_page()
        if oldpage != newpage:
            self.prevButton.set_sensitive(True)
            if newpage+1 == self.notebook.get_n_pages():
                self.nextButton.set_label(self.finishStockId)       
            self.emit("forward",newpage)
            

#------------------------------------------------------------------------------- 
class GraduatedColours(Wizard):
    """ A wizard to help create maps with color gradients.
        """

    def __init__(self):
        Wizard.__init__(self,_("Create Graduated Colour Map"),PGButler.instance.win,
            gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
        
        self.tables = filter(lambda t: t.hasGeometry, PGButler.instance.paneDbinfo.info.tables)
        self.values = []
             
        # -------------------------------------------------------- create page 0
        page0 = gtk.VBox()
        
        #---------------------------- top area
        vb = gtk.VBox()
        
        # select a table
        hbox = gtk.HBox()
        hbox.pack_start( createLabel(_("<b>Table: </b>")) , False )
        self.comboboxTable = gtk.combo_box_new_text()
        for t in self.tables:
            if t.schema == "public": n = t.name
            else: n = "%s.%s"%(t.schema,t.name)
            if len(n) > 20: n = n[:20]+"...";
            self.comboboxTable.append_text(n)
        hbox.pack_start(self.comboboxTable)
        self.comboboxTable.connect("changed", self.set_combo_column)
        vb.pack_start(hbox)
        
        # select a column by which to classify
        hbox = gtk.HBox()
        hbox.pack_start( createLabel(_("<b>Column: </b>")), False )
        self.comboboxColumn = gtk.combo_box_new_text()
        self.comboboxColumn.connect("changed", self.reset_intervals)
        
        vbox = gtk.VBox()
        vbox.pack_start(self.comboboxColumn,False)
        #self.rangeLabel = gtk.Label()
        #vbox.pack_start(self.rangeLabel, False)
        hbox.pack_start(vbox)
        vb.pack_start(hbox)
        
        hbox = gtk.HBox()
        hbox.pack_start(vb,False)
        page0.pack_start( hbox, False, False )
        
        #---------------------------- lower area
        
        # --- buttons
        hbox = gtk.HBox()
        
        button = gtk.Button( _("Swap") )
        button.connect("clicked", self.swap_order)
        hbox.pack_end( button, False )
        
        self.intervals = gtk.SpinButton()
        self.intervals.set_range(1,100)
        self.intervals.set_value(5)
        self.intervals.set_increments(1,1)
        
        self.intervalsize = gtk.Entry();

        button = gtk.Button( _("Classify") )
        button.connect("clicked", self.reset_intervals)
        
        hbox.pack_end( button, False )    
        hbox.pack_end( self.intervals, False )
        hbox.pack_end( createLabel(_("# of Intervals: ")), False )

        #------------------------------------

        button = gtk.Button( _("Test Equal Int") )
        button.connect("clicked", self.reset_equal_intervals)
        hbox.pack_end( button, False )    

        hbox.pack_end( self.intervalsize, False )

        page0.pack_start( gtk.HSeparator(), False, False, 6 )
        page0.pack_start(hbox, False)

        # --- treeview
        self.columntype = int
        
        # the listtore contains: gtk.gdk.color, from, to, label
        self.liststore = gtk.ListStore(gobject.TYPE_PYOBJECT, str, str, str)
        tv = gtk.TreeView( self.liststore )
        tv.connect("row-activated",self.__on_row_activated)
        
        cr = gtk.CellRendererPixbuf()
        col = gtk.TreeViewColumn( _("Colour"), cr )  
        col.set_cell_data_func(cr, self.__pixbuf_cell_renderer)     
        tv.append_column(col)
                
        ctr = gtk.CellRendererText()
        ctr.set_property("editable",True)
        ctr.connect("edited",self.__on_value_edited, 1)
        col = gtk.TreeViewColumn( _("From"), ctr, text=1 )
        col.set_resizable(True)
        tv.append_column(col)

        ctr = gtk.CellRendererText()
        ctr.set_property("editable",True)
        ctr.connect("edited",self.__on_value_edited, 2)
        col = gtk.TreeViewColumn( _("To"), ctr, text=2 )
        col.set_resizable(True)
        tv.append_column(col)

        ctr = gtk.CellRendererText()
        ctr.set_property("editable",True)
        ctr.connect("edited",self.__on_label_edited, 3)
        col = gtk.TreeViewColumn( _("Label"), ctr, text=3 )
        col.set_resizable(True)
        tv.append_column(col)
        
        sw = gtk.ScrolledWindow()
        sw.set_shadow_type(gtk.SHADOW_IN)
        sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
        sw.add(tv)
        page0.pack_start(sw)
    
        # --- colours
        hbox = gtk.HBox()

        self.startColour = gtk.ColorButton( gtk.gdk.Color(65535,65535,0) )
        #self.startColour.set_use_alpha(True)
        hbox.pack_end(self.startColour,False)

        self.endColour = gtk.ColorButton( gtk.gdk.Color(65535,0,0) )
        #self.endColour.set_use_alpha(True)        
        hbox.pack_end(self.endColour,False)        
        
        hbox.pack_end( createLabel(_("Colours for Interpolation: ")), False )
        self.startColour.connect("color-set", self.reset_colours )
        self.endColour.connect("color-set", self.reset_colours )

        self.updateColours = gtk.ToggleButton("mantain colours");
        self.updateColours.set_active(True)
        hbox.pack_start( self.updateColours, False )

        page0.pack_start(hbox, False)
                
        # -------------------------------------------------------- create page 1
        page1 = gtk.VBox()
        page1.pack_start( gtk.Label( _("A map will be created from the following queries:") ), False ) 
        self.sqlqueries = gtk.ListStore(str)
        tv = gtk.TreeView(self.sqlqueries)
        ctr = gtk.CellRendererText()
        ctr.set_property("editable",True)
        ctr.connect("edited", self.__on_sql_query_edited)
        tv.append_column( gtk.TreeViewColumn(_("SQL Query"), ctr, text=0) )
        sw = gtk.ScrolledWindow()
        sw.set_shadow_type(gtk.SHADOW_IN)
        sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
        sw.add(tv)
        page1.pack_start( sw )
             
        # -------- start the wizard
        self.connect("forward", self.forward)
        self.append_page(page0)
        self.append_page(page1)

        self.nextButton.set_sensitive(False)


    def swap_order(self, widget=None):
        last = None
        for i in range( self.liststore.iter_n_children(None) ):
            it = self.liststore.get_iter_first()
            self.liststore.move_before(it,last)
            last = it


    def reset_colours(self, widget=None):
        """ This should depend on the actually used values. """

        if len(self.values) == 0: return
        
        print "-"*30

        # first, normalize the values: we have n number pairs, but we only
        # have n-1 colours
        values = []
        it = self.liststore.get_iter_first()
        while it is not None:
            a = float(self.liststore.get_value(it,1))
            b = float(self.liststore.get_value(it,2))
            mean = a + b / 2
            values.append(mean)
            print mean
            it = self.liststore.iter_next(it)

        print "="*10

        # convert value breaks to percents

        if len(values) <= 1:
            percents = [0,]
        elif len(values) == 2:
            percents = [0,100]
        else: # at least three:

            # first and last entry are always 100%

            valuetotal = values[-1] - values[0]
            percents = [0,]
            for value in values[1:-1]:
                n = float(value) / float(valuetotal) * 100.0
                percents.append(n)
            percents.append(100)

        for p in percents:
            print "%i%%"%p

        print "="*10
    
        def make_list( a, b ):
            """ Interpolate from a to b using intervals from the current liststore. """
            if a > b: a,b = b,a
            t = b-a;

            #print a,"to",b

            out = []
            #out.append(a)
            for i in percents:
                n = a + (float(t) / 100.0 * i)
                #print n
                out.append(n)

            #print "-"*10

            return out

        #parts = self.liststore.iter_n_children(None)
        #def make_list( a, b ):
            #if a > b: a,b = b,a
            #r = b - a
            #if parts > 1: step = r / (parts-1)
            #else: step = 0
    
            #out = []
            #v = a
            #for i in range(parts-1):
                #out.append(v)
                #v += step
            #v = b
            #out.append(v)
            #return out
        
        # interpolate colours
        a = self.startColour.get_color()
        b = self.endColour.get_color()
        
        reds = make_list( a.red, b.red )
        greens = make_list( a.green, b.green )
        blues = make_list( a.blue, b.blue )
        
        colours = []
        for i in range(len(reds)):
            colours.append( gtk.gdk.Color(reds[i],greens[i],blues[i]) )

        # loop through the rows of the liststore
        it = self.liststore.get_iter_first()
        c = 0
        while it is not None:
            layer = Layer()
            
            table = self.tables[self.comboboxTable.get_active()]
            layer.geometryType = table.getGeomType()   
            
            if layer.geometryType is None: # special case for broken tables
                layer.geometryType = "multi"  
                layer.style.line = colour_from_gdk(colours[c])
                layer.style.fill = colour_from_gdk(colours[c])
            elif layer.geometryType == "line":
                layer.style.line = colour_from_gdk(colours[c])
            else:
                layer.style.fill = colour_from_gdk(colours[c])
            
            self.liststore.set_value(it,0,layer)

            it = self.liststore.iter_next(it)
            c += 1


    def reset_equal_intervals(self, widget=None):
        cursor = PGButler.instance.db.handle.cursor()
        minimum, maximum = 0,0

        try: interval = int(self.intervalsize.get_text())
        except: return        

        # get all numeric columns in the table
        try:
            table = self.tables[self.comboboxTable.get_active()]
            c = table.get_numeric_columns()[self.comboboxColumn.get_active()]
            column = c[0]
            if c[1][:3] == "int": self.columntype = int
            else: self.columntype = float
        except: return
            
        # get minimum and maximum
        try:
            cursor.execute("SELECT min(%s) FROM %s"%(column ,table.getSQL()))
            minimum = cursor.fetchall()[0][0]
        except: pass

        try:             
            cursor.execute("SELECT max(%s) FROM %s"%(column ,table.getSQL()))
            maximum = cursor.fetchall()[0][0]
        except: pass    

        # compute value breaks
        self.values = []
        self.values.append(minimum)  
        i = minimum
        while i < maximum:
            i += interval   
            self.values.append(i)

        # fill liststore
        c = 0
        self.liststore.clear()
        for v in self.values:
            if c+1 < len(self.values):
                it = self.liststore.append( [None, v, self.values[c+1], ""] )
                self.generate_label( it )
            c += 1
            
        # create a colour gradient
        self.reset_colours()
        self.nextButton.set_sensitive(True)



            
    def reset_intervals(self, widget=None):
        cursor = PGButler.instance.db.handle.cursor()
        minimum, maximum = 0,0
        
        # get all numeric columns
        try:
            table = self.tables[self.comboboxTable.get_active()]
            c = table.get_numeric_columns()[self.comboboxColumn.get_active()]
            column = c[0]
            if c[1][:3] == "int": self.columntype = int
            else: self.columntype = float
        except: return
            
        try:
            cursor.execute("SELECT min(%s) FROM %s"%(column ,table.getSQL()))
            minimum = cursor.fetchall()[0][0]
        except: pass

        try:             
            cursor.execute("SELECT max(%s) FROM %s"%(column ,table.getSQL()))
            maximum = cursor.fetchall()[0][0]
        except: pass
        
        #-----------------
        #self.rangeLabel.set_markup(_("<i>values range from %s to %s</i>"%(minimum,maximum)))
        #print "min:",minimum,"max:",maximum 
        
        # adjust the widget to some special cases
        #if maximum-minimum < self.intervals.get_value() and self.columntype == int:
            #self.intervals.set_value( int(maximum-minimum) )
        #if maximum == minimum: self.intervals.set_value( 1 )

        # get the number of regular breaks from the widget
        parts = int(self.intervals.get_value())
        
        # compute value breaks
        self.values = []
        if parts > 1: step = (maximum-minimum)/(parts)
        else: step = 0
        v = minimum
        for i in range(parts):
            self.values.append(v)
            v += step
        self.values.append(maximum) # to make sure last value == maximum
        
        # fill liststore
        c = 0
        self.liststore.clear()
        for v in self.values:
            if c+1 < len(self.values):
                it = self.liststore.append( [None, v, self.values[c+1], ""] )
                self.generate_label( it )
            c += 1
            
        # create a colour gradient
        self.reset_colours()
        self.nextButton.set_sensitive(True)


    def set_combo_column(self, widget=None):
        active = self.comboboxTable.get_active()
        if active == -1:
            self.comboboxColumn.get_model().clear()
        else:
            self.comboboxColumn.get_model().clear()
            
            for col in self.tables[active].get_numeric_columns():
                n = col[0]
                if(len(n)>20): n = n[:20]+"..."
                self.comboboxTable.append_text(n)

                self.comboboxColumn.append_text(col[0])
                self.comboboxColumn.set_active(0) # <- easiest
            self.reset_intervals()

    def finish(self):
        it = self.sqlqueries.get_iter_first()
        it2 = self.liststore.get_iter_first()
        
        layer = self.liststore.get_value(it2, 0)
        layer.name = self.liststore.get_value(it2, 3)
                    
        sql = self.sqlqueries.get_value(it, 0)
        PGButler.instance.runQuery(sql,layer)
        result = layer.resultset
            
        it = self.sqlqueries.iter_next(it)
        it2 = self.liststore.iter_next(it2)
        while it is not None:
            sql = self.sqlqueries.get_value(it, 0)
            
            layer = self.liststore.get_value(it2, 0)
            layer.name = self.liststore.get_value(it2, 3)
            
            PGButler.instance.runQuery(sql,layer,result)
            
            it = self.sqlqueries.iter_next(it)
            it2 = self.liststore.iter_next(it2)
            
    def generate_label(self, it):
        lower = self.liststore.get_value(it,1)
        upper = self.liststore.get_value(it,2)
        label = _("%s - %s")%(lower,upper)
        self.liststore.set_value( it, 3, label )   

    def forward(self, widget, page):
        if page == 1:
        
            self.sqlqueries.clear()
        
            try:
                table = self.tables[self.comboboxTable.get_active()]
                c = table.get_numeric_columns()[self.comboboxColumn.get_active()]
                column = c[0]
            except: return        
        
            it = self.liststore.get_iter_first()
            q = 'SELECT * FROM %s WHERE "%s" >= %s AND "%s" < %s;'
            while it is not None:
                lower = self.liststore.get_value(it, 1)
                upper = self.liststore.get_value(it, 2)
                sql = q%(table.getSQL(), column, lower, column, upper)
                self.sqlqueries.append( (sql,) )
                it = self.liststore.iter_next(it)
            
            
    def __pixbuf_cell_renderer(self, column, cell, model, it):
        layer = model.get_value(it, 0)
        if layer is None: return
        pixbuf = createLayerIcon( layer )
        #pixbuf = createColourPixbuf(color)
        cell.set_property("pixbuf", pixbuf  )
       
    def __on_row_activated(self, treeview, path, view_column):
        it = self.liststore.get_iter(path)
        
        d = gtk.ColorSelectionDialog( _("Color") )
        #d.colorsel.set_has_palette(True)
        #d.colorsel.set_has_opacity_control(True)
        
        layer = self.liststore.get_value(it,0)
        if layer.geometryType == "line": colour = layer.style.line.asGdkColor()
        else: colour = layer.style.fill.asGdkColor()
        d.colorsel.set_current_color( colour )
        
        response = d.run()
        if response == gtk.RESPONSE_OK:
            colour = colour_from_gdk(d.colorsel.get_current_color())
            if layer.geometryType == "line": layer.style.line = colour
            else: colour = layer.style.fill = colour
        d.destroy()

    def __on_value_edited(self, crt, path, new_text, column):
        try:
            it = self.liststore.get_iter(path)
            v = self.columntype(new_text) #try to cast to int or to float
            self.liststore.set_value( it, column, v )
            self.generate_label(it)
            if self.updateColours.get_active(): self.reset_colours()
        except ValueError:
            pass
            
    def __on_sql_query_edited(self, crt, path, new_text):
        it = self.sqlqueries.get_iter(path)
        self.sqlqueries.set_value( it, 0, new_text )
                
    def __on_label_edited(self, crt, path, new_text, column):
        it = self.liststore.get_iter(path)
        self.liststore.set_value( it, column, new_text )
            
#-------------------------------------------------------------------------------        
class AutoQueries:
    """ A wizard to create a map of graduated colours """
    
    
    def __init__(self):

        # hook into the existing ui
        ui = """
        <ui>
            <menubar name="MenuBar">
                <menu action="Tools">
                    <placeholder name="Tools_1">
                        <menuitem action="GraduatedColoursAction"/>
                    </placeholder>
                </menu>    
           </menubar>
        </ui>
        """

        # register actions        
        self.i18n = { "graduatedcolours": _("Graduated Colours") }
        actiongroup = gtk.ActionGroup("AutoQueriesGroup")
        actiongroup.add_actions([
            ('GraduatedColoursAction', None, self.i18n["graduatedcolours"],
                None, self.i18n["graduatedcolours"], self.graduatedColours ),
        ])
        
        # load ui into PGButler
        PGButler.instance.uimanager.insert_action_group(actiongroup, 0)
        PGButler.instance.uimanager.add_ui_from_string(ui)
        
    def graduatedColours(self, widget):
        d = GraduatedColours()
        d.run()
        d.destroy()

PGButler.instance.register_extension( AutoQueries )
